"""
从 Bilbao Crystallographic Server 直接获取点群特征标表数据
"""

import json
import time
from pathlib import Path

import requests
import urllib3
from bs4 import BeautifulSoup

# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 新的 Bilbao 服务器地址
BILBAO_BASE_URL = "http://webbdcrista2.ehu.es/cgi-bin/rep/programs/sam/point.py"


def load_point_group_mappings():
    """加载点群映射数据（sg 和 num 参数）"""
    params_file = Path(__file__).parent.parent / "data" / "bilbao_character_tables" / "point_group_bilbao_params.json"
    with open(params_file, "r", encoding="utf-8") as f:
        return json.load(f)


def extract_character_table(soup):
    """从 BeautifulSoup 对象中提取特征标表"""
    # 查找包含 "Character Table" 的表格
    tables = soup.find_all("table")

    for table in tables:
        # 检查是否是特征标表
        caption = table.find("caption")
        if not caption or "Character Table" not in caption.get_text():
            continue

        rows = table.find_all("tr")
        if len(rows) < 2:
            continue

        # 第一行：点群名称、# 和操作符号（最后一列是 functions，跳过）
        first_row = rows[0]
        cells = first_row.find_all(["th", "td"])
        operations = [cell.get_text().strip() for cell in cells[2:-1]]

        # 剩余行：不可约表示
        irreps = []
        for row in rows[1:]:
            cells = row.find_all(["th", "td"])
            if len(cells) < 3:
                continue

            mulliken_text = cells[0].get_text().strip()
            gamma_text = cells[1].get_text().strip()

            # 获取特征值（跳过最后一列 functions）
            char_cells = cells[2:-1]

            # 检查是否是合并的 E 型表示
            # Bilbao 格式：Mulliken 列如 "1E2E" 或 "1E 2E"，没有空格或有空格
            # Gamma 列如 "Γ3Γ2"
            # 特征列如 "11", "w2w" 等（两个字符紧挨着）

            # 检测是否包含多个 E（如 "1E2E", "2E'1E'", "1Eg2Eg" 等）
            if "E" in mulliken_text and mulliken_text.count("E") >= 2:
                # 提取 Mulliken 标签
                # 处理各种格式：1E2E, 2E'1E', 1Eg2Eg, 2E''1E'' 等
                import re

                # 匹配模式：数字+E+可选的修饰符（g/u/'/''）
                mulliken_pattern = r"(\d*E[gu]?[\'\"]*)"
                mulliken_labels = re.findall(mulliken_pattern, mulliken_text)

                # 提取 Gamma 标签
                gamma_labels = gamma_text.replace(" ", "").split("Γ")[1:]
                gamma_labels = [f"Γ{g}" for g in gamma_labels if g]

                # 拆分特征值
                # Bilbao 的 HTML 中，两个 E 型表示的字符用 <br/> 分隔
                # 使用 get_text(separator="|") 在 <br/> 处插入分隔符
                chars1 = []
                chars2 = []
                for char_cell in char_cells:
                    # 使用 "|" 作为分隔符获取文本，这样 <br/> 会被替换为 "|"
                    text_with_sep = char_cell.get_text(separator="|", strip=True)
                    parts = [p.strip() for p in text_with_sep.split("|") if p.strip()]

                    # 应该有偶数个部分（每个表示一个片段）
                    # 如果有上标（如 w<sup>2</sup>），会被拆成多个部分
                    # 我们需要智能地合并它们

                    if len(parts) == 2:
                        # 最简单的情况：两个完整的值
                        chars1.append(parts[0])
                        chars2.append(parts[1])
                    elif len(parts) == 3:
                        # 可能是 ["w", "2", "w"] 或 ["w", "w", "2"]
                        # 需要判断中间的是数字（上标）还是独立字符
                        if parts[1].isdigit():
                            # ["w", "2", "w"] -> "w2" 和 "w"
                            chars1.append(parts[0] + parts[1])
                            chars2.append(parts[2])
                        elif parts[2].isdigit():
                            # ["w", "w", "2"] -> "w" 和 "w2"
                            chars1.append(parts[0])
                            chars2.append(parts[1] + parts[2])
                        else:
                            # 无法判断，使用原始文本
                            full_text = char_cell.get_text().strip()
                            chars1.append(full_text)
                            chars2.append(full_text)
                    elif len(parts) == 4:
                        # 可能是 ["-w", "2", "-w", "2"] -> "-w2" 和 "-w2"
                        if parts[1].isdigit() and parts[3].isdigit():
                            chars1.append(parts[0] + parts[1])
                            chars2.append(parts[2] + parts[3])
                        else:
                            # 尝试两两合并
                            chars1.append(parts[0] + parts[1])
                            chars2.append(parts[2] + parts[3])
                    elif len(parts) == 1:
                        # 没有 <br/>，两个表示相同
                        chars1.append(parts[0])
                        chars2.append(parts[0])
                    else:
                        # 其他情况，使用原始文本
                        full_text = char_cell.get_text().strip()
                        chars1.append(full_text)
                        chars2.append(full_text)

                # 添加两个表示
                if len(mulliken_labels) >= 2:
                    irreps.append(
                        {
                            "mulliken": mulliken_labels[0],
                            "gamma": gamma_labels[0] if len(gamma_labels) > 0 else "Γ?",
                            "characters": chars1,
                        }
                    )
                    irreps.append(
                        {
                            "mulliken": mulliken_labels[1],
                            "gamma": gamma_labels[1] if len(gamma_labels) > 1 else "Γ?",
                            "characters": chars2,
                        }
                    )
                else:
                    # 解析失败，作为普通表示处理
                    characters = [cell.get_text().strip() for cell in char_cells]
                    irreps.append(
                        {"mulliken": mulliken_text, "gamma": gamma_text.replace(" ", ""), "characters": characters}
                    )
            else:
                # 普通表示（A, B, T 等）
                characters = [cell.get_text().strip() for cell in char_cells]
                irreps.append(
                    {"mulliken": mulliken_text, "gamma": gamma_text.replace(" ", ""), "characters": characters}
                )

        if irreps:
            return {"operations": operations, "irreps": irreps}

    return None


def fetch_point_group(group_info):
    """获取单个点群的数据"""
    schoenflies = group_info["schoenflies"]
    sg = group_info["sg"]
    num = group_info["num"]

    # 构建新的 URL
    url = f"{BILBAO_BASE_URL}?sg={sg}&num={num}"

    print(f"  {schoenflies:5s} (sg={sg}, num={num})...", end=" ", flush=True)

    try:
        # 请求页面
        response = requests.get(url, timeout=30, verify=False)
        response.raise_for_status()

        # 检查是否是错误页面
        if "error" in response.text.lower() or len(response.text) < 500:
            print("❌ 服务器返回错误或空页面")
            return None

        # 解析 HTML
        soup = BeautifulSoup(response.text, "html.parser")

        # 提取特征标表
        character_table = extract_character_table(soup)

        if not character_table:
            print("❌ 未找到特征标表")
            return None

        print(f"✅ 成功 (operations: {len(character_table['operations'])}, irreps: {len(character_table['irreps'])})")

        return {
            "schoenflies": schoenflies,
            "sg": sg,
            "num": num,
            "character_table": character_table,
            "source": "Bilbao Crystallographic Server",
            "source_url": url,
        }

    except Exception as e:
        print(f"❌ 错误: {str(e)[:50]}")
        return None


def main():
    """主函数：批量获取所有点群数据"""
    # 加载点群映射
    point_groups = load_point_group_mappings()

    # 输出目录（直接保存到 data 目录）
    output_dir = Path(__file__).parent.parent / "data"
    output_dir.mkdir(exist_ok=True)

    print("=" * 80)
    print("从 Bilbao Crystallographic Server 提取点群特征标表")
    print("=" * 80)
    print(f"服务器地址: {BILBAO_BASE_URL}")
    print(f"总共 {len(point_groups)} 个点群")
    print(f"输出目录: {output_dir}")
    print()

    all_data = []
    results = {}

    for group_info in point_groups:
        data = fetch_point_group(group_info)

        if data:
            all_data.append(data)
            results[group_info["schoenflies"]] = "success"
        else:
            results[group_info["schoenflies"]] = "failed"

        # 避免请求过快
        time.sleep(0.5)

    # 保存所有数据
    output_file = output_dir / "bilbao_point_groups.json"
    with open(output_file, "w", encoding="utf-8") as f:
        json.dump(all_data, f, ensure_ascii=False, indent=2)

    print()
    print("=" * 80)
    print("提取完成!")
    print(f"  成功: {sum(1 for v in results.values() if v == 'success')} 个")
    print(f"  失败: {sum(1 for v in results.values() if v == 'failed')} 个")
    print(f"  已保存到: {output_file}")
    print("=" * 80)

    # 显示失败的点群
    failed = [k for k, v in results.items() if v == "failed"]
    if failed:
        print()
        print(f"❌ 失败的点群 ({len(failed)} 个):")
        print("  ", ", ".join(failed))

    return results, all_data


if __name__ == "__main__":
    main()
